#include <iostream>
#include <sstream>
#include <typeinfo>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
#include "ruby-m3g-internal.hpp"
using namespace m3g;
using namespace std;



namespace {
    struct AnimationTrackAccessor {
        Object3D* object3d;
    };
    VALUE rb_cObject3D_AnimationTrackAccessor;
}


void ruby_Object3D_mark (Object3D* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_Object3D_free (Object3D* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~Object3D ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_Object3D_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(Object3D));
    return Data_Wrap_Struct (self, ruby_Object3D_mark, ruby_Object3D_free, p);
}

VALUE ruby_Object3D_initialize (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    new (p) Object3D();
    p->setExportedEntity ((void*)self);
    return self;
}


VALUE ruby_Object3D_add_animation_track (VALUE self, VALUE val_animation_track)
{
    if (!rb_obj_is_kind_of (val_animation_track, rb_cAnimationTrack)) {
        rb_raise (rb_eArgError, "wrong argument type (expected AnimationTrack)");
    }
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    AnimationTrack* anim_track;
    Data_Get_Struct (val_animation_track, AnimationTrack, anim_track);

    __TRY__;
    p->addAnimationTrack (anim_track);
    __CATCH__;

    return Qnil;
}

VALUE ruby_Object3D_animate (VALUE self, VALUE val_world_time)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    int world_time = NUM2INT(val_world_time);
    int ret;
    __TRY__;
    ret = p->animate (world_time);
    __CATCH__;

    return INT2NUM(ret);
}


VALUE ruby_Object3D_duplicate (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);

    Object3D* obj;
    __TRY__;
    obj = p->duplicate();
    __CATCH__;

    vector<Object3D*> objs;
    extract_all_references (obj, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        if (objs[i]->getExportedEntity() == 0) {
            VALUE val_obj;
            if      (typeid(*objs[i]) == typeid(AnimationController)) val_obj = Data_Wrap_Struct(rb_cAnimationController, ruby_AnimationController_mark, ruby_AnimationController_free, objs[i]);
            else if (typeid(*objs[i]) == typeid(AnimationTrack))      val_obj = Data_Wrap_Struct(rb_cAnimationTrack,      ruby_AnimationTrack_mark,      ruby_AnimationTrack_free,      objs[i]);
            else if (typeid(*objs[i]) == typeid(Appearance))          val_obj = Data_Wrap_Struct(rb_cAppearance,          ruby_Appearance_mark,          ruby_Appearance_free,          objs[i]);
            else if (typeid(*objs[i]) == typeid(Background))          val_obj = Data_Wrap_Struct(rb_cBackground,          ruby_Background_mark,          ruby_Background_free,          objs[i]);
            else if (typeid(*objs[i]) == typeid(CompositingMode))     val_obj = Data_Wrap_Struct(rb_cCompositingMode,     ruby_CompositingMode_mark,     ruby_CompositingMode_free,     objs[i]);
            else if (typeid(*objs[i]) == typeid(Fog))                 val_obj = Data_Wrap_Struct(rb_cFog,                 ruby_Fog_mark,                 ruby_Fog_free,                 objs[i]);
            else if (typeid(*objs[i]) == typeid(Image2D))             val_obj = Data_Wrap_Struct(rb_cImage2D,             ruby_Image2D_mark,             ruby_Image2D_free,             objs[i]);
            else if (typeid(*objs[i]) == typeid(TriangleStripArray))  val_obj = Data_Wrap_Struct(rb_cTriangleStripArray,  ruby_TriangleStripArray_mark,  ruby_TriangleStripArray_free,  objs[i]);
            else if (typeid(*objs[i]) == typeid(KeyframeSequence))    val_obj = Data_Wrap_Struct(rb_cKeyframeSequence,    ruby_KeyframeSequence_mark,    ruby_KeyframeSequence_free,    objs[i]);
            else if (typeid(*objs[i]) == typeid(Material))            val_obj = Data_Wrap_Struct(rb_cMaterial,            ruby_Material_mark,            ruby_Material_free,            objs[i]);
            else if (typeid(*objs[i]) == typeid(PolygonMode))         val_obj = Data_Wrap_Struct(rb_cPolygonMode,         ruby_PolygonMode_mark,         ruby_PolygonMode_free,         objs[i]);
            else if (typeid(*objs[i]) == typeid(Camera))              val_obj = Data_Wrap_Struct(rb_cCamera,              ruby_Camera_mark,              ruby_Camera_free,              objs[i]);
            else if (typeid(*objs[i]) == typeid(Group))               val_obj = Data_Wrap_Struct(rb_cGroup,               ruby_Group_mark,               ruby_Group_free,               objs[i]);
            else if (typeid(*objs[i]) == typeid(World))               val_obj = Data_Wrap_Struct(rb_cWorld,               ruby_World_mark,               ruby_World_free,               objs[i]);
            else if (typeid(*objs[i]) == typeid(Light))               val_obj = Data_Wrap_Struct(rb_cLight,               ruby_Light_mark,               ruby_Light_free,               objs[i]);
            else if (typeid(*objs[i]) == typeid(Mesh))                val_obj = Data_Wrap_Struct(rb_cMesh,                ruby_Mesh_mark,                ruby_Mesh_free,                objs[i]);
            else if (typeid(*objs[i]) == typeid(MorphingMesh))        val_obj = Data_Wrap_Struct(rb_cMorphingMesh,        ruby_MorphingMesh_mark,        ruby_MorphingMesh_free,        objs[i]);
            else if (typeid(*objs[i]) == typeid(SkinnedMesh))         val_obj = Data_Wrap_Struct(rb_cSkinnedMesh,         ruby_SkinnedMesh_mark,         ruby_SkinnedMesh_free,         objs[i]);
            else if (typeid(*objs[i]) == typeid(Sprite3D))            val_obj = Data_Wrap_Struct(rb_cSprite3D,            ruby_Sprite3D_mark,            ruby_Sprite3D_free,            objs[i]);
            else if (typeid(*objs[i]) == typeid(Texture2D))           val_obj = Data_Wrap_Struct(rb_cTexture2D,           ruby_Texture2D_mark,           ruby_Texture2D_free,           objs[i]);
            else if (typeid(*objs[i]) == typeid(VertexArray))         val_obj = Data_Wrap_Struct(rb_cVertexArray,         ruby_VertexArray_mark,         ruby_VertexArray_free,         objs[i]);
            else if (typeid(*objs[i]) == typeid(VertexBuffer))        val_obj = Data_Wrap_Struct(rb_cVertexBuffer,        ruby_VertexBuffer_mark,        ruby_VertexBuffer_free,        objs[i]);
            else rb_raise(rb_eRuntimeError, "Duplicated object is invalid, name=%s.", typeid(*objs[i]).name());

            objs[i]->setExportedEntity ((void*)val_obj);
        }
    }

    return (VALUE)obj->getExportedEntity();
}

VALUE ruby_Object3D_find (VALUE self, VALUE val_user_id)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    int user_id = NUM2INT (val_user_id);

    Object3D* obj;
    __TRY__;
    obj = p->find (user_id);
    __CATCH__;

    return obj ? (VALUE)obj->getExportedEntity() : Qnil;
}

VALUE ruby_Object3D_get_animation_track (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    AnimationTrackAccessor* accessor;
    VALUE  val_accessor = Data_Make_Struct (rb_cObject3D_AnimationTrackAccessor, AnimationTrackAccessor, 0, -1, accessor);
    accessor->object3d = p;
    return val_accessor;
}

VALUE ruby_Object3D_get_animation_track_count (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);

    int count;
    __TRY__;
    count = p->getAnimationTrackCount ();
    __CATCH__;

    return INT2NUM(count);
}

VALUE ruby_Object3D_get_references (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
  
    int num;
    __TRY__;
    num = p->getReferences (0);
    __CATCH__;

    Object3D** objs = (Object3D**)ruby_xmalloc (sizeof(Object3D*)*num);
    __TRY__;
    p->getReferences (objs);
    __CATCH__;

    VALUE val_objs = rb_ary_new2 (num);
    for (int i = 0;i < num; i++) {
        rb_ary_push (val_objs, (VALUE)objs[i]->getExportedEntity());
    }
    ruby_xfree (objs);
  
    return val_objs;
}

VALUE ruby_Object3D_get_user_id (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    int id;
    __TRY__;
    id = p->getUserID ();
    __CATCH__;

    return INT2NUM(id);
}

VALUE ruby_Object3D_get_user_object (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    void* ptr;
    __TRY__;
    ptr = p->getUserObject ();
    __CATCH__;

    return ptr ? (VALUE)ptr : Qnil;
}

VALUE ruby_Object3D_remove_animation_track (VALUE self, VALUE val_animation_track)
{
    if (!rb_obj_is_kind_of (val_animation_track, rb_cAnimationTrack)) {
        rb_raise (rb_eArgError, "wrong argument type (expected AnimationTrack)");
    }

    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    AnimationTrack* anim_track;
    Data_Get_Struct (val_animation_track, AnimationTrack, anim_track);

    __TRY__;
    p->removeAnimationTrack (anim_track);
    __CATCH__;

    return Qnil;
}

VALUE ruby_Object3D_set_user_id (VALUE self, VALUE val_id)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    int id = NUM2INT (val_id);
  
    __TRY__;
    p->setUserID (id);
    __CATCH__;

    return Qnil;
}

VALUE ruby_Object3D_set_user_object (VALUE self, VALUE val_user_object)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    __TRY__;
    p->setUserObject ((void*)val_user_object);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Object3D_print (VALUE self)
{
    Object3D* p;
    Data_Get_Struct (self, Object3D, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 * Object3D_AnimTrackAccessor_Accessor
 */

VALUE ruby_Object3D_AnimationTrackAccessor_get_animation_track (VALUE self, VALUE val_index)
{
    AnimationTrackAccessor* p;
    Data_Get_Struct (self, AnimationTrackAccessor, p);
    int index = NUM2INT (val_index);

    AnimationTrack* anim_track;
    __TRY__;
    anim_track = p->object3d->getAnimationTrack (index);
    __CATCH__;
    return anim_track ? (VALUE)anim_track->getExportedEntity() : Qnil;
}

VALUE ruby_Object3D_AnimationTrackAccessor_add_animation_track (VALUE self, VALUE val_animation_track)
{
    if (!rb_obj_is_kind_of (val_animation_track, rb_cAnimationTrack)) {
        rb_raise (rb_eArgError, "wrong argument type (expected AnimationTrack)");
    }

    AnimationTrackAccessor* p;
    Data_Get_Struct (self, AnimationTrackAccessor, p);
    AnimationTrack* animation_track;
    Data_Get_Struct (val_animation_track, AnimationTrack, animation_track);

    __TRY__;
    p->object3d->addAnimationTrack (animation_track);
    __CATCH__;
    return self;
}



/**
 * Object3DクラスのRubyへの登録.
 */
void register_Object3D ()
{
    // Object3D
    rb_cObject3D            = rb_define_class_under (rb_mM3G, "Object3D",            rb_cObject);

    rb_define_alloc_func (rb_cObject3D, ruby_Object3D_allocate);
    rb_define_private_method (rb_cObject3D, "initialize",     (VALUE(*)(...))ruby_Object3D_initialize,                0);


    rb_define_method (rb_cObject3D, "animate",                (VALUE(*)(...))ruby_Object3D_animate,                   1);
    rb_define_method (rb_cObject3D, "duplicate",              (VALUE(*)(...))ruby_Object3D_duplicate,                 0);
    rb_define_method (rb_cObject3D, "find",                   (VALUE(*)(...))ruby_Object3D_find,                      1);
    rb_define_method (rb_cObject3D, "animation_track",        (VALUE(*)(...))ruby_Object3D_get_animation_track,       0);
    rb_define_method (rb_cObject3D, "animation_track_count",  (VALUE(*)(...))ruby_Object3D_get_animation_track_count, 0);
    rb_define_method (rb_cObject3D, "references",             (VALUE(*)(...))ruby_Object3D_get_references,            0);
    rb_define_method (rb_cObject3D, "user_id",                (VALUE(*)(...))ruby_Object3D_get_user_id,               0);
    rb_define_method (rb_cObject3D, "user_object",            (VALUE(*)(...))ruby_Object3D_get_user_object,           0);
    rb_define_method (rb_cObject3D, "remove_animation_track", (VALUE(*)(...))ruby_Object3D_remove_animation_track,    1);
    rb_define_method (rb_cObject3D, "user_id=",               (VALUE(*)(...))ruby_Object3D_set_user_id,               1);
    rb_define_method (rb_cObject3D, "user_object=",           (VALUE(*)(...))ruby_Object3D_set_user_object,           1);
    rb_define_method (rb_cObject3D, "to_s",                   (VALUE(*)(...))ruby_Object3D_print,                     0);

    // Object3D_AnimationTrackAccessor
    rb_cObject3D_AnimationTrackAccessor  = rb_define_class_under (rb_cObject3D, "AnimationTrackAccessor", rb_cObject);

    rb_define_method (rb_cObject3D_AnimationTrackAccessor, "[]",        (VALUE(*)(...))ruby_Object3D_AnimationTrackAccessor_get_animation_track,    1);
    rb_define_method (rb_cObject3D_AnimationTrackAccessor, "<<",         (VALUE(*)(...))ruby_Object3D_AnimationTrackAccessor_add_animation_track,   1);

}
